package org.pulloid;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

/**
 * Allows to set a public field's value or a non-public field's value through a setter method.
 *
 * @author Romain Laboisse labrom@gmail.com
 */
class Setter<T> {
	
	@SuppressWarnings("unchecked")
	private static final Collection<Class<?>> SUPPORTED_TYPES = new ArrayList<Class<?>>(Arrays.asList(int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class));
	
	private Field field;
	private Class<?> fieldType;
	protected Method setterMethod;

	/**
	 * 
	 * @param targetType
	 * @param fieldName
	 */
	Setter(Class<T> targetType, String fieldName) {
		this(targetType, fieldName, null);
	}

	/**
	 * 
	 * @param targetType
	 * @param fieldName
	 * @param targetFieldType The actual type of the field to set. It can be null.
	 */
	Setter(Class<T> targetType, String fieldName, Class<?> targetFieldType) {
		try {
			// We don't need to know the field type since we're just looking for the member field with the right name
			this.field = targetType.getField(fieldName);
			this.fieldType = field.getType();
			// If value is not a number and field is a number, the setter will try to convert
			return;
		} catch(NoSuchFieldException nsfe) {
			// No such field, trying setter method
			// First search, with the specified field type or String
			String setterMethodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
			this.fieldType = targetFieldType != null ? targetFieldType : String.class; // If not type specified, String is assumed
			try {
				this.setterMethod = targetType.getMethod(setterMethodName, this.fieldType);
				return;
			} catch (SecurityException se) {
				throw new CursorException(se);
			} catch (NoSuchMethodException nsme) {
				// Second search, on all methods, filter using name and number of params (1)
				for(Method m : targetType.getMethods()) {
					if(m.getName().equals(setterMethodName)) {
						Class<?>[] params = m.getParameterTypes();
						if(params.length == 1 && SUPPORTED_TYPES.contains(params[0])) { // Guess we found the right setter method
							this.setterMethod = m;
							this.fieldType = params[0];
							return;
						}
					}
				}
			}
			throw new CursorException(String.format("Unable to find a field or a setter method for field %1$s in class %2$s", fieldName, targetType.getName()));
		}
	}


	
	final Object parseValue(Object value) {
		if(fieldType == null)
			return value;
		if(fieldType == Integer.class)
			return new Integer(String.valueOf(value));
		if(fieldType == Long.class)
			return new Long(String.valueOf(value));
		if(fieldType == Float.class)
			return new Float(String.valueOf(value));
		if(fieldType == Double.class)
			return new Double(String.valueOf(value));
		if(fieldType == int.class)
			return Integer.parseInt(String.valueOf(value));
		if(fieldType == long.class)
			return Long.parseLong(String.valueOf(value));
		if(fieldType == float.class)
			return Float.parseFloat(String.valueOf(value));
		if(fieldType == double.class)
			return Double.parseDouble(String.valueOf(value));
		return value;
	}

	boolean set(Object target, Object value) {
		if(field != null) {
			// TODO Transform value is needed
			try {
				field.set(target, parseValue(value));
				return true;
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else if(setterMethod != null) {
			
			// TODO Transform value is needed
			try {
				setterMethod.invoke(target, parseValue(value));
				return true;
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return false;
	}
	
	String getFieldName() {
		if(field != null)
			return field.getName();
		
		String field = setterMethod.getName().substring(3);
		field = field.substring(0, 1).toLowerCase() + field.substring(1);
		return field;
	}

}